/*-------------------------------------------------------------------------
 - Copyright (c) 2024-2025 [XD-AMCC TEAM]
 - [XD-AMCC] is licensed under Mulan PSL v2.
 - You can use this software according to the terms and conditions of the Mulan PSL v2.
 - You may obtain a copy of Mulan PSL v2 at:
 -          http://license.coscl.org.cn/MulanPSL2
 - THIS SOFTWARE IS PROVIDED ON AN "AS IS" BASIS, WITHOUT WARRANTIES
 - OF ANY KIND, EITHER EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED
 - TO NON-INFRINGEMENT, MERCHANTABILITY OR FIT FOR A PARTICULAR PURPOSE.
 - See the Mulan PSL v2 for more details.
 ------------------------------------------------------------------------*/


/**
 * @brief Helper for check all array declarations,
 *               for dump init-values of array into file.
 *
 * @author  WXQ#XDU
 * @date    2024.12
 */

#include "_stb_inner.h"

#define  ARRAY_MAX_LENGTH   0x7FFFFFU  // max number of elements in an array

// Check the size of an array.
// Return true only if the array size is too big.
bool stb_array_checkSize(t_attr_directDeclarator * attr)
{
    if (NULL == attr->dimensions) return false; // not array

    bool   nElementsTooBig = false;
    size_t       nElements = attr->dimensions[1];
    if (nElements < 1) {
        nElements = 1;
    } else if (nElements > ARRAY_MAX_LENGTH) {
        nElementsTooBig = true;
    }

    if ( false  == nElementsTooBig ) {
        int dimLength;
        for (int dimCounter = 2; dimCounter <= attr->dimensions[0]; ++dimCounter) {
            dimLength = attr->dimensions[dimCounter];
            if (nElements <= ARRAY_MAX_LENGTH / dimLength)
                nElements *= dimLength;
            else {
                nElementsTooBig = true;
                break;
            }
        }
    }

    if( nElementsTooBig ) {
        t_token_ptr    idToken = tree_get_token(attr->idNode);
        t_position_ptr     pos = token_get_position(idToken);
        ERROR_REPORT_INPUT(pos->filePath, pos->line, pos->column,
                           EC_DIM_TOOBIG, token_get_text(idToken));
        attr->hasError = true;
    }
    return  (nElementsTooBig);
}


//----------------------------------------------------------------------------
//
//  All codes below are helpers for dump the initializer-list of an array
//


// Working data for output the initial value of arrays
typedef struct  output_info_ {
    const int  * defDimension;
    const char * arrName;
    FILE * fp;

    t_type_ptr  elementType;
    int         arrDimension;
    int         initExprCounter;
} t_output_info;


static void output_arr_info_(t_output_info * outputInfo, t_position_ptr pos,
                             t_tree_ptr initializerNode);
static int output_init_expr(t_list_app_value app_data, void * user_data);


//
// Dump the non-empty initial values for an array with initializer.
// Input:
//     the symbol entry of an array
// Output:
//     a text file, its name pattern is defined
//     by macro FN_ARRINIT_FORMAT (amcc_config.h).
//
void  array_init_dump(t_symentry_ptr pSymEntry ) {
    t_tree_ptr initializerNode = symentry_var_initializer(pSymEntry);
    t_type_ptr     varType     = symentry_type(pSymEntry);
    const int * dimensions     = type_var_diminfo(varType);

    if (  NULL == initializerNode    // no initializer
          || NULL == dimensions   )  // variable is not an array
        return;

    t_tree_ptr  arrayNameNode = symentry_treenode(pSymEntry);
    t_position  pos =  tree_get_startPosition(arrayNameNode);
    char        filePath[FILEPATH_MAXSIZE + 1];
    char        fileName[100];
    snprintf(fileName, sizeof(fileName)-1, FN_ARRINIT_FORMAT, pos.line, pos.column);
    snprintf(filePath, sizeof(filePath)-1, "%s/%s", amcc_get_outputDir(), fileName);
    FILE * fp = fopen(filePath, "wt");
    if ( NULL == fp ) {
        ERROR_REPORT_FILEOP(EC_OPENFILE, filePath);
        return;
    }

    t_output_info  outputInfo;
    outputInfo.elementType  = type_var_base(varType);
    outputInfo.defDimension = dimensions;
    outputInfo.arrDimension = outputInfo.defDimension[0];
    outputInfo.arrName      = tree_get_markText(arrayNameNode);
    outputInfo.fp           = fp;
    outputInfo.initExprCounter = 0;
    output_arr_info_( &outputInfo, &pos, initializerNode );

    t_var_symInfo_ptr pVarInfo = symentry_var_info(pSymEntry);
    t_list_ptr       initExprs = pVarInfo->initExprs;

#define  HEAD_FLAG_TEXT  "\ninit-value-exprs: "
    if (NULL == initExprs) {
        fprintf(fp, HEAD_FLAG_TEXT "EMPTY\n");
    } else {
        int n = list_size(initExprs);
        fprintf(fp, HEAD_FLAG_TEXT "------------BEGIN %d values\n", n);
        list_foreach( initExprs, output_init_expr, &outputInfo);
        fprintf(fp, HEAD_FLAG_TEXT "------------END %d values\n", n);
    }
#undef   HEAD_FLAG_TEXT

    fclose ( fp );

    log_debug("Dumped the init-values of array \"%s\" defined at [%d:%d] into file %s",
              tree_get_markText(arrayNameNode),
              pos.line,   pos.column,  filePath );

}


// dump basic information of an array, they are in file-header
static void output_arr_info_(t_output_info * outputInfo, t_position_ptr pos,
                             t_tree_ptr initializerNode)
{
    FILE * fp = outputInfo->fp ;
    fprintf(fp, "// position is  %d  %d\n", pos->line, pos->column);
    fprintf(fp, "// arr_name   element_type  dimension  len_each_dim\n");
    fprintf(fp, "%s  %s  %d",
            outputInfo->arrName,
            type_name(outputInfo->elementType),
            outputInfo->arrDimension );
    for(int k=1; k <= outputInfo->arrDimension; ++k) {
        fprintf(fp, "  %d", outputInfo->defDimension[k]);
    }
    fprintf(fp, "\n");

    if (NULL != initializerNode) {
        t_position startPosition = tree_get_startPosition(initializerNode );
        fprintf(fp, "arr-initializerNode = %tx, position is %d %d\n",
                (ptrdiff_t)initializerNode,
                startPosition.line,         startPosition.column);
    } else {
        fprintf(fp, "arr-initializerNode = 0\n");
    }
}


static void output_init_expr_body(t_output_info * outputInfo,
                                  t_tree_ptr exprNode);

// dump one init-value-expr with its subscripts
static int output_init_expr(t_list_app_value app_data, void * user_data) {
    t_init_expr_ptr   initExpr = (t_init_expr_ptr)app_data;
    t_output_info * outputInfo = (t_output_info *)user_data;

    // dump the indexes for current init-value-expr
    fprintf(outputInfo->fp, "\nIEXPR %3d_th [",
            ++(outputInfo->initExprCounter));
    for (int k=1; k <= outputInfo->arrDimension; ++k) {
        fprintf(outputInfo->fp, " %d ",  initExpr->indexHolders[k]);
    }
    fprintf(outputInfo->fp, "]\n");

    // dump the init-value-expr
    output_init_expr_body(outputInfo, initExpr->exprNode);
    return 0;
}



// -----------------------------------------------------------------------
//  Helpers for generate a string of an expression in suffix form.
//
//
// This work is not difficult, but it's a bit verbose:
// 1. We should walk through the parse tree of an expression,
// 2. We should merge the strings of sub-expressions into suffix form, so
// 3. Before merging, we should store the strings of sub-expressions into
//    a form of collection/table before merging.
//


// ---------- Attribute table contains all <expr_tree_node, string_of_expr_node> pairs

typedef struct expr_serializer {
    t_tree_listener base;
    t_treemap_ptr attributeTable;    // a hash-table
} t_expr_serializer;


static void attrTable_free_(t_map_key K, t_map_value V) {
    t_string_ptr txt = (t_string_ptr)V;
    string_destroy(txt);
}

static t_string_ptr attrTable_get_(t_tree_listener_ptr listenerPtr, t_tree_ptr node) {
    t_expr_serializer * serializerPtr = (t_expr_serializer *)listenerPtr;
    t_map_value V = treemap_get(serializerPtr->attributeTable, node);
    return (t_string_ptr)(V);
}

static void attrTable_put_(t_tree_listener_ptr listenerPtr, t_tree_ptr node, t_string_ptr txt) {
    t_expr_serializer * serializerPtr = (t_expr_serializer *)listenerPtr;
    treemap_put(serializerPtr->attributeTable, node, txt );
}


// ------------- parse-tree visit actions for my local tree-walker ----------


#define  OPTEXT_FUNCALL   "()"
#define  OPTEXT_ARRREF    "[]"
#define  OPTEXT_UNARY_MINUS   "@"
#define  OPTEXT_UNARY_PLUS    "$"


static void exit_expression(t_tree_listener_ptr pThis, t_tree_ptr node) {
    t_tree_ptr  childNode0 = tree_get_child(node, 0);
    t_string_ptr exprText  = attrTable_get_(pThis, childNode0);
    attrTable_put_(pThis, node, string_clone(exprText));
}

static void exit_leftValue(t_tree_listener_ptr pThis, t_tree_ptr node) {
    int          nChildren = tree_get_childCount(node);
    t_tree_ptr      idNode = tree_get_child(node, 0);
    const char *   varName = tree_get_markText(idNode);
    t_string_ptr  exprString = NULL;

    if (nChildren < 2) {    // this is a scalar name, not an array
        exprString = string_new(varName, strlen(varName));
        attrTable_put_(pThis, node, exprString);
        return;
    }

// The following code is missing validation and handling of string length boundaries.

    // a reference to one element of an array, like arr[e1][e2]...[eN],
    // creates a string like  arr e1 e2 ... eN  N []
    char buff[EXPRTEXT_MAXLEN+1];
    char * pDest = buff;
    const char * pSrc;
    int  len = 0;
    sprintf(pDest, "%s", varName);
    len += (int)strlen(pDest);
    pDest += len;
    for (int k = 2; k < nChildren; k += 3 ) {
        t_tree_ptr  subscriptNode = tree_get_child(node, k);
        t_string_ptr subscriptString = attrTable_get_(pThis, subscriptNode);
        pSrc = string_text(subscriptString);
        sprintf(pDest, " %s", pSrc);
        len += (int)strlen(pDest); //1 + (int)strlen(pSrc);
        pDest = buff + len;
    }

    sprintf(pDest, " %d " OPTEXT_ARRREF, nChildren / 3);
    len += (int)strlen(pDest);
    exprString = string_new(buff, len);
    attrTable_put_(pThis, node, exprString);
}


// action for all binary operators
static void do_binaryop_(t_tree_listener_ptr pThis, t_tree_ptr node) {
    int  nChildren =  tree_get_childCount(node);
    if ( nChildren == 1 ) {   // pass the attribute in bottom-up
        t_string_ptr lhsText ;
        lhsText = attrTable_get_(pThis, tree_get_child(node, 0));
        attrTable_put_(pThis, node, string_clone(lhsText));
        return;
    }

    // parse tree node of a binary operator, the number of children may be
    //   1 :  no any operator
    //   3 :  one operator, like "a op b"
    // 2k+1:  more operators, like "a op b op c op d ... op n"

    t_list_ptr   listChildPtr  = tree_get_children(node);
    t_tree_ptr * childrenArray = (t_tree_ptr *)list_to_array( listChildPtr );
    if (NULL == childrenArray) {
        ERROR_REPORT_MALLOC();
        return;
    }

// The following code is missing validation and handling of string length boundaries.

    char buff[EXPRTEXT_MAXLEN+1];
    char * pDest = buff;
    size_t len = 0;
    buff[0] = '\0';

    t_string_ptr lhsText ,  rhsText ;
    lhsText = attrTable_get_(pThis, childrenArray[0]);
    sprintf(pDest, "%s", string_text(lhsText));
    len += string_length(lhsText);
    pDest += len;

    const char * optrText;
    for (int i = 1; i < nChildren; i += 2 ) {
        optrText = (char*) tree_get_markText(childrenArray[i]);
        rhsText = attrTable_get_(pThis, childrenArray[i + 1]);

        sprintf(pDest, " %s %s", string_text(rhsText), optrText);
        len += strlen(pDest);
        pDest = buff + len;
    }

    attrTable_put_(pThis, node, string_new(buff, len));
    free(childrenArray);
}

static void exit_assignmentExpr(t_tree_listener_ptr pThis, t_tree_ptr node) {
    do_binaryop_ (pThis, node);
}
static void exit_logicalOrExpr(t_tree_listener_ptr pThis, t_tree_ptr node) {
    do_binaryop_ (pThis, node);
}
static void exit_logicalAndExpr(t_tree_listener_ptr pThis, t_tree_ptr node) {
    do_binaryop_ (pThis, node);
}
static void exit_equalityExpr(t_tree_listener_ptr pThis, t_tree_ptr node) {
    do_binaryop_ (pThis, node);
}
static void exit_relationalExpr(t_tree_listener_ptr pThis, t_tree_ptr node) {
    do_binaryop_ (pThis, node);
}
static void exit_additiveExpr(t_tree_listener_ptr pThis, t_tree_ptr node) {
    do_binaryop_ (pThis, node);
}
static void exit_multiplicativeExpr(t_tree_listener_ptr pThis, t_tree_ptr node) {
    do_binaryop_ (pThis, node);
}

static void exit_unaryExpr(t_tree_listener_ptr pThis, t_tree_ptr node) {
    t_tree_ptr   childNode0 = tree_get_child(node, 0);
    t_string_ptr exprText ;

    // for: unaryExpr -> primaryExpr,  pass the attribute in bottom-up
    if (tree_is_nonTerminal(childNode0)) {
        exprText = attrTable_get_(pThis, childNode0);
        attrTable_put_(pThis, node, string_clone(exprText));
        return;
    }

// The following code is missing validation and handling of string length boundaries.

    // for: unaryExpr -> (+|-|!) expr
    // output : expr [+-!]
    char buff[EXPRTEXT_MAXLEN+1];
    const char * opTxt ;
    opTxt = tree_get_markText(tree_get_child(node, 0));
    exprText = attrTable_get_(pThis, tree_get_child(node, 1));
    sprintf(buff, " %s %s", string_text(exprText),
            (opTxt[0] == '-' ? OPTEXT_UNARY_MINUS :
            (opTxt[0] == '+' ? OPTEXT_UNARY_PLUS : opTxt))
    );

    exprText = string_new(buff, strlen(buff));
    attrTable_put_(pThis, node, exprText);
}


static void exit_primaryExpr(t_tree_listener_ptr pThis, t_tree_ptr node)
{
    t_tree_ptr childNode0 = tree_get_child(node, 0);
    EnumTokenKind  operandType = tree_get_tokenKind(childNode0);
    const char * exprText = tree_get_markText(childNode0);
    t_string_ptr exprString = NULL;
    switch (operandType){
        case TK_INT_LITERAL:
        case TK_REAL_LITERAL:
            exprString = string_new(exprText, strlen(exprText));
            break;
        case TK_LPAREN:
            exprString = attrTable_get_(pThis, tree_get_child(node, 1));
            exprString = string_clone(exprString);
            break;
        default:
            exprString = attrTable_get_(pThis, childNode0);
            exprString = string_clone(exprString);
            break;
    }

    attrTable_put_(pThis, node, exprString);
}


// generates: arg1 arg2 ... argN  N  func_name ()
static void exit_functionCall(t_tree_listener_ptr pThis, t_tree_ptr node)
{
    char buff[EXPRTEXT_MAXLEN+1];
    t_tree_ptr       idNode = tree_get_child(node, 0);
    const char *   funcName = tree_get_markText(idNode);
    t_tree_ptr  argListNode = tree_get_child(node, 2);
    int           nChildren = tree_get_childCount(argListNode);
    t_string_ptr exprString = NULL;

    // empty argument list
    if (tree_is_epsilon(tree_get_child(argListNode, 0))) {
        sprintf(buff, "0 %s " OPTEXT_FUNCALL, funcName);
        exprString = string_new(buff, strlen(buff));
        attrTable_put_(pThis, node, exprString);
        return;
    }

// The following code is missing validation and handling of string length boundaries.

    // one or more arguments like: arg1 , arg2 , ... , argN
    int nArguments = (nChildren + 1) / 2;
    t_tree_ptr   argNode;
    t_string_ptr argString;
    const char * pSrc;
    char * pDest = buff;
    int      len = 0;
    for (int argIdx = 0; argIdx < nArguments;  argIdx += 2){
        argNode = tree_get_child(argListNode, argIdx);
        argString = attrTable_get_(pThis, argNode);
        pSrc = string_text(argString);
        sprintf(pDest, argIdx==0 ? "%s" : " %s", pSrc );
        len += (int)strlen(pDest);
        pDest = buff + len;
    }

    sprintf(pDest, " %d %s " OPTEXT_FUNCALL, nArguments, funcName);
    len += (int)strlen(pDest);
    exprString = string_new(buff, len);
    attrTable_put_(pThis, node, exprString);
}


static t_expr_serializer *  expr_serializer_create() {
    t_expr_serializer * pSerializer = malloc(sizeof(t_expr_serializer));
    tree_listener_init0((t_tree_listener_ptr) pSerializer,
                        sizeof(t_expr_serializer) );

    pSerializer->attributeTable = treemap_new(20, attrTable_free_);

    // overrides super visit methods
    pSerializer->base.exit_expression = exit_expression;
    pSerializer->base.exit_leftValue = exit_leftValue;
    pSerializer->base.exit_assignmentExpression = exit_assignmentExpr;
    pSerializer->base.exit_logicalOrExpression = exit_logicalOrExpr;
    pSerializer->base.exit_logicalAndExpression = exit_logicalAndExpr;
    pSerializer->base.exit_equalityExpression = exit_equalityExpr;
    pSerializer->base.exit_relationalExpression = exit_relationalExpr;
    pSerializer->base.exit_additiveExpression = exit_additiveExpr;
    pSerializer->base.exit_multiplicativeExpression = exit_multiplicativeExpr;
    pSerializer->base.exit_unaryExpression = exit_unaryExpr;
    pSerializer->base.exit_primaryExpression = exit_primaryExpr;
    pSerializer->base.exit_functionCall = exit_functionCall;

    return pSerializer;
}

static void expr_serializer_destroy(t_expr_serializer * pSerializer) {
    treemap_destroy( pSerializer->attributeTable );
    free(pSerializer);
}


// Output an expression as suffix form
static void output_init_expr_body(t_output_info * outputInfo, t_tree_ptr exprNode)
{
    t_expr_serializer * pSerializer = expr_serializer_create();

    // generate the suffix form
    tree_walk(exprNode, (t_tree_listener_ptr)pSerializer);
    t_string_ptr resultText = attrTable_get_((t_tree_listener_ptr) pSerializer, exprNode);
    const char * exprTxt = string_text(resultText);

    FILE * fp = outputInfo->fp ;
    t_type_ptr  exprType = EXPRTYPE_GET(exprNode);
    fprintf(fp, "%s { %s }\n", type_name(exprType), exprTxt);

    expr_serializer_destroy(pSerializer);
}
